package ua.org.extjsbuilder.parser;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Writer;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Map;

import ua.org.extjsbuilder.parser.runtime.JSGlobalToken;
import ua.org.extjsbuilder.parser.scanner.CodeBlocksScanner;
import ua.org.extjsbuilder.parser.scanner.CommentsAndLiteralsScanner;
import ua.org.extjsbuilder.parser.scanner.LinesScanner;

public class JSParser {
	private static final int BUFFER_SIZE = 1024;
	private static Map<Thread,JSParser> parsers = new HashMap<Thread, JSParser>();
	
	private Reader input;
	private IRuntimeBuilder global;
	private IRuntime runtime;
	private Thread thread;
	private JSErrorHandler handler;
	
	public JSParser(Reader input) throws IOException{
		this.input = input;
		this.global = new JSGlobalToken(input);
		this.runtime = null;
		this.thread = Thread.currentThread();
	}
	
	public JSParser(InputStream input) throws IOException {
		this(new InputStreamReader(input));
	}
	/**
	 * This method should be call only from within parser.
	 * @return parser for current thread.
	 */
	public static JSParser getParser(){
		return parsers.get(Thread.currentThread());
	}

	public void parse(JSErrorHandler handler){
		if(thread!=Thread.currentThread()){
			throw new ConcurrentModificationException(
					"You should run parser from the same thread " +
					"it is necessary to avoid synchronization");
		}
		
		if(handler==null){
			throw new IllegalArgumentException("You should provide not NULL error handler");
		}
		this.handler = handler;
		
		parse();
	}
	
	public JSErrorHandler getErrorHandler(){
		return handler;
	}

	private void parse() {
		parsers.put(thread,this);
		handler.clear();
		try{
			global.scan(new LinesScanner());
			global.scan(new CommentsAndLiteralsScanner());
			global.scan(new CodeBlocksScanner());
			runtime = global.createRuntime(null,handler);
			runtime.execute(global,null);
		}
		catch(JSParseException ex){
			handler.error(ex.getMessage(),ex.getLine());
		}
		finally{
			parsers.remove(thread);
		}
	}

	public void compile(Writer out, boolean debug, boolean optimize) throws IOException {
		if(runtime==null)return;
		if(debug){
			writeDebugVersion(out);
			return;
		}
		runtime.compile(out, optimize);
	}

	private void writeDebugVersion(Writer out) {
		try {
			int len = 0;
			char[] cbuf = new char[BUFFER_SIZE];
			while((len=input.read(cbuf))>0){
				out.write(cbuf,0,len);
			}
		}
		catch(IOException ex){
			throw new JSParseException(ex);
		}
	}
}
